/*
 * 从FreeBSD导入的文件，原作Poul-Henning Kamp。
 *
 * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.5 1999/12/17 20:21:45 peter Exp $
 *
 * contrib/pgcrypto/crypt-md5.c
 */

#include "postgres.h"

#include "px-crypt.h"
#include "px.h"

#define MD5_SIZE 16

static const char _crypt_a64[] =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

static void fc__crypt_to64(char *fc_s, unsigned long fc_v, int fc_n)
{
	while (--fc_n >= 0)
	{
		*fc_s++ = _crypt_a64[fc_v & 0x3f];
		fc_v >>= 6;
	}
}

/*
 * UNIX密码
 */

char * px_crypt_md5(const char *fc_pw, const char *fc_salt, char *fc_passwd, unsigned fc_dstlen)
{
	static char *fc_magic = "$1$"; /* 这个字符串对该算法具有特殊意义。
								 * 这样处理，我们可以在以后获得更好的结果
								 * */
	static char *fc_p;
	static const char *fc_sp,
			   *fc_ep;
	unsigned char fc_final[MD5_SIZE];
	int			fc_sl,
				fc_pl,
				fc_i;
	PX_MD	   *fc_ctx,
			   *fc_ctx1;
	int			fc_err;
	unsigned long fc_l;

	if (!fc_passwd || fc_dstlen < 120)
		return NULL;

	/* 首先细化盐 */
	fc_sp = fc_salt;

	/* 如果它以魔法字符串开头，则跳过该部分 */
	if (strncmp(fc_sp, fc_magic, strlen(fc_magic)) == 0)
		fc_sp += strlen(fc_magic);

	/* 在第一个'$'处停止，最大8个字符 */
	for (fc_ep = fc_sp; *fc_ep && *fc_ep != '$' && fc_ep < (fc_sp + 8); fc_ep++)
		continue;

	/* 获取真实盐的长度 */
	fc_sl = fc_ep - fc_sp;

	/* 我们需要两个PX_MD对象 */
	fc_err = px_find_digest("md5", &fc_ctx);
	if (fc_err)
		return NULL;
	fc_err = px_find_digest("md5", &fc_ctx1);
	if (fc_err)
	{
		/* 在低内存情况下，这条路径是可能的 */
		px_md_free(fc_ctx);
		return NULL;
	}

	/* 首先是密码，因为这是最未知的 */
	px_md_update(fc_ctx, (const uint8 *) fc_pw, strlen(fc_pw));

	/* 然后是我们的魔法字符串 */
	px_md_update(fc_ctx, (uint8 *) fc_magic, strlen(fc_magic));

	/* 然后是原始盐 */
	px_md_update(fc_ctx, (const uint8 *) fc_sp, fc_sl);

	/* 然后是MD5(pw,salt,pw)的字符数 */
	px_md_update(fc_ctx1, (const uint8 *) fc_pw, strlen(fc_pw));
	px_md_update(fc_ctx1, (const uint8 *) fc_sp, fc_sl);
	px_md_update(fc_ctx1, (const uint8 *) fc_pw, strlen(fc_pw));
	px_md_finish(fc_ctx1, fc_final);
	for (fc_pl = strlen(fc_pw); fc_pl > 0; fc_pl -= MD5_SIZE)
		px_md_update(fc_ctx, fc_final, fc_pl > MD5_SIZE ? MD5_SIZE : fc_pl);

	/* 不要留下任何它们可以使用的虚拟内存。 */
	px_memset(fc_final, 0, sizeof fc_final);

	/* 然后是一些真的很奇怪的东西... */
	for (fc_i = strlen(fc_pw); fc_i; fc_i >>= 1)
		if (fc_i & 1)
			px_md_update(fc_ctx, fc_final, 1);
		else
			px_md_update(fc_ctx, (const uint8 *) fc_pw, 1);

	/* 现在生成输出字符串 */
	strcpy(fc_passwd, fc_magic);
	strncat(fc_passwd, fc_sp, fc_sl);
	strcat(fc_passwd, "$");

	px_md_finish(fc_ctx, fc_final);

	/*
	 * 现在，为了确保事情不会运行得太快，在一台60 MHz
	 * 的Pentium上，这需要34毫秒，所以你需要30秒来构建一个
	 * 1000条目的字典...
	 */
	for (fc_i = 0; fc_i < 1000; fc_i++)
	{
		px_md_reset(fc_ctx1);
		if (fc_i & 1)
			px_md_update(fc_ctx1, (const uint8 *) fc_pw, strlen(fc_pw));
		else
			px_md_update(fc_ctx1, fc_final, MD5_SIZE);

		if (fc_i % 3)
			px_md_update(fc_ctx1, (const uint8 *) fc_sp, fc_sl);

		if (fc_i % 7)
			px_md_update(fc_ctx1, (const uint8 *) fc_pw, strlen(fc_pw));

		if (fc_i & 1)
			px_md_update(fc_ctx1, fc_final, MD5_SIZE);
		else
			px_md_update(fc_ctx1, (const uint8 *) fc_pw, strlen(fc_pw));
		px_md_finish(fc_ctx1, fc_final);
	}

	fc_p = fc_passwd + strlen(fc_passwd);

	fc_l = (fc_final[0] << 16) | (fc_final[6] << 8) | fc_final[12];
	fc__crypt_to64(fc_p, fc_l, 4);
	fc_p += 4;
	fc_l = (fc_final[1] << 16) | (fc_final[7] << 8) | fc_final[13];
	fc__crypt_to64(fc_p, fc_l, 4);
	fc_p += 4;
	fc_l = (fc_final[2] << 16) | (fc_final[8] << 8) | fc_final[14];
	fc__crypt_to64(fc_p, fc_l, 4);
	fc_p += 4;
	fc_l = (fc_final[3] << 16) | (fc_final[9] << 8) | fc_final[15];
	fc__crypt_to64(fc_p, fc_l, 4);
	fc_p += 4;
	fc_l = (fc_final[4] << 16) | (fc_final[10] << 8) | fc_final[5];
	fc__crypt_to64(fc_p, fc_l, 4);
	fc_p += 4;
	fc_l = fc_final[11];
	fc__crypt_to64(fc_p, fc_l, 2);
	fc_p += 2;
	*fc_p = '\0';

	/* 不要留下任何它们可以使用的虚拟内存。 */
	px_memset(fc_final, 0, sizeof fc_final);

	px_md_free(fc_ctx1);
	px_md_free(fc_ctx);

	return fc_passwd;
}
